#!node

const fs = require('fs');
const path = require('path');
const tsNode = require('ts-node');
const glob = require('glob');
const yargs = require('yargs/yargs');
const { hideBin } = require('yargs/helpers');

const args = yargs(hideBin(process.argv))
    .usage('Usage: $0 [package path] --type [type] --allowed-ext [extention(s)]')
    .option('type', { type: 'string', default: 'core' })
    .option('allowed-ext', { array: true, type: 'string' })
    .parse();

tsNode.register();

const targetConfig = process.env.NX_TASK_TARGET_CONFIGURATION || 'development';

console.log(targetConfig, process.env.BUILD_GRID_VERSION);
const expectedGridVersion =
    (targetConfig === 'production' || targetConfig === 'archive') && process.env.ENV !== 'local'
        ? process.env.BUILD_GRID_VERSION
        : JSON.parse(fs.readFileSync('./package.json').toString()).version;

const type = args.type;

const dir = args._[0];
if (!fs.readdirSync(dir)) {
    console.error("Can't find directory: " + dir);
    process.exit(1);
}

const packageJsonFile = fs.readFileSync(path.join(dir, 'package.json'));
if (!packageJsonFile) {
    console.error("Can't find package.json: " + dir);
    process.exit(1);
}

const packageContents = glob.sync('**/*', { cwd: dir, nodir: true, dot: true });
const packageJson = JSON.parse(packageJsonFile.toString());

let exitStatus = 0;

async function check(type, field, filename) {
    if (!(typeof filename === 'string')) return;

    let success = true;
    if (type === 'types' && !filename.endsWith('.d.ts')) {
        console.warn(`[${packageJson.name}] ${filename}: Field '${field}' has reference to non '.d.ts' file.`);
        success = false;
    }

    if (type === 'cjs') {
        if (
            (!filename.endsWith('.cjs') && !filename.endsWith('.cjs.js') && !filename.endsWith('.js')) ||
            filename.includes('.esm')
        ) {
            console.warn(
                `[${packageJson.name}] ${filename}: Field '${field}' has reference to non CJS file: ${filename}`
            );
            success = false;
        }
    }

    if (type === 'esm' && !filename.endsWith('.esm.js') && !filename.endsWith('.mjs')) {
        console.warn(`[${packageJson.name}] ${filename}: Field '${field}' has reference to non ESM file: ${filename}`);
        success = false;
    }

    if (type === 'js' && !filename.endsWith('.js') && !filename.endsWith('.mjs') && !filename.endsWith('.cjs')) {
        console.warn(`[${packageJson.name}] ${filename}: Field '${field}' has reference to non JS file: ${filename}`);
        success = false;
    }

    let fileCount = 0;
    if (filename.indexOf('*') >= 0) {
        const matches = glob.sync(path.join(dir, filename));
        fileCount += matches.length;
        if (matches.length === 0) {
            console.warn(
                `[${packageJson.name}] ${filename}: Field '${field}' has invalid file reference glob: ${filename}`
            );
            success = false;
        }
    } else if (fs.existsSync(path.join(dir, filename))) {
        fileCount++;
    } else {
        console.warn(`[${packageJson.name}] ${filename}: Field '${field}' has invalid file reference: ${filename}`);
        success = false;
    }

    if (success) {
        console.log(`[${packageJson.name}] ${filename} check success. [${fileCount} matches]`);
    } else {
        exitStatus = 1;
    }
}

const checkedFiles = [];
function checkOneExists(...filenames) {
    if (!filenames.some((f) => fs.existsSync(path.join(dir, f)))) {
        console.log(`[${packageJson.name}]: Didn't find any files with name(s): ${filenames.join(' / ')}`);
        exitStatus = 1;
    }

    checkedFiles.push(...filenames);
}

const exportMap = {
    require: 'cjs',
    import: 'esm',
    default: 'js',
    types: 'types',
};

async function checkExports(exports) {
    for (const key in exports) {
        if (key === './package.json') {
            // Generated by Angular build tool
            await check('json', key, exports[key]);
            continue;
        }

        if (typeof exports[key] === 'object') {
            await checkExports(exports[key]);
            continue;
        }

        const type = exportMap[key] ?? 'unknown';
        await check(type, key, exports[key]);
    }
}

const allowedExtensions = ['.md', '.js', '.mjs', '.d.ts', '.txt', '.json', '.png', '.jpg', '.html'];
if (args.allowedExt) {
    console.log(`Adding extensions to whitelist: ` + args.allowedExt.join(' '));
    allowedExtensions.push(...args.allowedExt.map((e) => `.${e}`));
}
function checkAllowedExtension(filename) {
    if (!allowedExtensions.some((ext) => filename.endsWith(ext))) {
        console.log(`[${packageJson.name}]: Unexpected file extension: ${filename}`);
        exitStatus = 1;
    }
}

function validatePackageVersions() {
    if (packageJson.version !== expectedGridVersion) {
        console.log(
            `[${packageJson.name}]: Version field mismatch, expected [${expectedGridVersion}] but found [${packageJson.version}]`
        );
        exitStatus = 1;
    }
}

async function validateCorePackageEntries() {
    for (const field of ['types', 'typing']) {
        const filename = packageJson[field];
        await check('types', field, filename);
    }
    for (const field of ['main']) {
        const filename = packageJson[field];
        await check('cjs', field, filename);
    }
    for (const field of ['module']) {
        const filename = packageJson[field];
        await check('esm', field, filename);
    }
}

async function validateStylesPackageEntries() {
    for (const field of ['main']) {
        const filename = packageJson[field];
        await check('css', field, filename);
    }
}

async function validateExports() {
    if (packageJson.exports != null) {
        await checkExports(packageJson.exports);
    }
}

function validateMandatoryNoneCodeFiles() {
    checkOneExists('README.md');
    checkOneExists('LICENSE.txt', 'LICENSE.html');
}

function validateCoreAllowedExtensions() {
    for (const file of packageContents) {
        checkAllowedExtension(file);
    }
}

async function run() {
    validatePackageVersions();

    switch (type) {
        case 'styles': {
            await validateStylesPackageEntries();
            validateMandatoryNoneCodeFiles();
            validateCoreAllowedExtensions();
            break;
        }
        case 'core':
        default: {
            await validateCorePackageEntries();
            await validateExports();
            validateMandatoryNoneCodeFiles();
            validateCoreAllowedExtensions();
        }
    }

    if (exitStatus === 0) {
        console.log(`[${packageJson.name}]: No problems found with package in ${dir}`);
    }
}

run()
    .then(() => {
        process.exit(exitStatus);
    })
    .catch((e) => {
        console.error(e);
        process.exit(1);
    });
